-- DesertAltarM
-- created by panyl
-- 沙漠祭坛

module("DesertAltarM", package.seeall);

local altars = {};
local options = {};

local whenLeaveDungeon = nil;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    altars = {};
    options = {};

    local desert_altar_CSV = get_desert_altar_CSV();
    for i = 1, #desert_altar_CSV do
        local row = desert_altar_CSV[i];

        altars[row.id] = row;
    end

    local desert_altar_options_CSV = get_desert_altar_options_CSV();
    for i = 1, #desert_altar_options_CSV do
        local row = desert_altar_options_CSV[i];

        options[row.id] = row;
    end
end

-- 模块初始化操作
function init()
    loadCsv();

    EventMgr.removeAll("DesertAltarM");

    EventMgr.register("DesertAltarM", event.LEAVE_DUNGEON, whenLeaveDungeon);
end

-- 离开迷宫回调
whenLeaveDungeon = function()
    -- 沙漠祭坛数据
    ME.user.dbase:delete("altar_option");

    -- 神灯选项
    ME.user.dbase:delete("desert_altar");

    -- 神灯加成信息
    ME.user.dbase:delete("lamp_prop");

    -- 灯神加成信息
    ME.user.dbase:delete("lamp_summon");
end

function query(optionId, path)
    local m = options[optionId];

    if not m or not path then
        return m;
    end

    return m[path];
end

function queryAltar(altarId, path)
    local m = altars[altarId];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 随机抽取某一类的选项
function getOption(class)
    local arr = {};
    local altarOption = ME.user.dbase:query("altar_option", {});
    local foreverOption = ME.user.dbase:query("forever_altar_option", {});

    for _, row in pairs(options) do
        repeat
            if row.classify ~= class then
                break;
            end

            -- 冈布奥选项需要特殊判断下
            if row.classify == "slime" and
                ME.user.pets[row["apply"]["slime"]] then
                break;
            end

            -- 是永久计数的选项，永久计数的选项不能是NUMBER_INFINITY的
            local times = foreverOption[row.id] or 0;
            if row.forever == 1 and times >= row.limit then
                break;
            end

            -- 只在沙漠绿洲出现的选项
            local dungeonId = DungeonM.getDungeonId();
            -- 获取主迷宫id
            dungeonId = DungeonAreaM.getParentId(dungeonId);
            if 1 == row.in_desert and dungeonId ~= 8 then
                break;
            elseif -1 == row.in_desert and dungeonId == 8 then
                break;
            end

            local id = row.id;
            local times = altarOption[id] or 0;
            if row.limit == NUMBER_INFINITY or
                times < row.limit then
                table.insert(arr, id);
            end
        until true;
    end

    local len = #arr;
    if len == 0 then
        return -1;
    end

    local rand = DungeonM.getRandSeed("get_desert_altar_option");
    table.sort(arr);

    return arr[rand % len + 1];
end

function fetchOptions(altarId)
    -- 如果是固定的
    if queryAltar(altarId, "regular") == 1 then
        local desertAltar = ME.user.dbase:query("desert_altar", {});
        if desertAltar[altarId] then
            return desertAltar[altarId];
        end
    end

    local list = table.deepcopy(queryAltar(altarId, "weight"));
    local num  = queryAltar(altarId, "option");
    local class;
    local ret = {};

    -- 1. 先去必定出现的选项
    local keys = table.keys(list);
    table.sort(keys);
    for index = 1, #keys do
        class = keys[index];
        if list[class] == -1 then
            -- 必定出现的，还要看当前是否还能出现
            local optionId = getOption(class);

            -- 选到一个选项
            if optionId > 0 then
                table.insert(ret, optionId);
            end

            -- 删除
            list[class] = nil;
        end
    end

    -- 选够了，直接返回
    if #ret >= num then
        table.sort(ret);
        return ret;
    end

    -- 2. 剩下的按概率抽取
    local arr = {};
    keys = table.keys(list);
    table.sort(keys);
    for index = 1, #keys do
        class = keys[index];
        local optionId = getOption(class);

        if optionId > 0 then
            table.insert(arr, optionId);
        end
    end

    -- 随机选
    num = num - #ret;

    -- 先排序
    table.sort(arr);
    local len = #arr;

    -- 随机num个
    while num > 0 do
        num = num - 1;

        if len <= 0 then
            break;
        end

        local index = DungeonM.getRandSeed("fetch_desert_altar_options") % len + 1;
        table.insert(ret, arr[index]);

        arr[index] = arr[len];
        len = len - 1;
    end

    table.sort(ret);
    return ret;
end

-- 抽取初始化祭坛的数据
function fetchAltarData()
    local ret = {};

    for id = 1, #altars do
        ret[id] = fetchOptions(id);
    end

    -- 记录一下
    ME.user.dbase:set("desert_altar", ret);

    return {["options"] = ret, };
end

-- 提交材料消耗
function calcSubmitCost(altarId)
    local m = queryAltar(altarId, "submit");
    local ret = {};

    for classId, count in pairs(m) do
        local prop = PropM.combine(ME.user, "reduce_cost", "lamp_oil");
        count = PropM.apply(prop, count);

        ret[classId] = count;
    end

    return ret;
end

-- 能否提交
function canSubmit(altarId)
    local m = calcSubmitCost(altarId);

    local useClassId = 0;
    for classId, count in pairs(m) do
        local amount;

        if type(classId) == "string" then
            amount = ME.user:queryAttrib(classId);
        elseif type(classId) == "number" then
            amount = ItemM.getAmount(ME.user, classId);
        end

        if amount < count then
            return false, classId;
        end

        useClassId = classId;
    end

    return true, useClassId;
end

-- 期望submit和selectOption不要分开做
-- 也就是，当玩家选择了愿望选项之后才submit、selectOption一起处理
-- 以免submit之后就暂离，这时候就需要保存数据
function submit(altarId)
    -- 先判断下
    if not canSubmit(altarId) then
        return false;
    end

    local cost = calcSubmitCost(altarId);

    for key, count in pairs(cost) do
        if type(key) == "number" then
            ItemM.costAmount(ME.user, key, count);

            -- 发布使用灯油的事件
            EventMgr.fire(event.USE_LAMP_OIL, { ["item_id"] = key });
        elseif type(key) == "string" then
            ME.user:costAttrib(key, count);
        end
    end

    -- 删除选项
    local desertAltar = ME.user.dbase:query("desert_altar", {});
    desertAltar[altarId] = nil;

    return true;
end

function calcApply(optionId)
    local apply = query(optionId, "apply");

    if type(apply) == "table" then
        return apply;
    end

    local formula;
    if type(apply) == "string" and string.startWith(apply, "formula_") then
        formula = string.sub(apply, string.len("formula_") + 1);
        return FormulaM.invoke(formula, optionId);
    end

    return {};
end

-- 选择某选项，这里不判断，调用的地方确保正确(服务器有判断)
function selectOption(altarId, optionId, pos)
    local m = calcApply(optionId);

    local grid = DungeonM.getGridByPos(pos);
    grid:changeState(GRID_STATE_DISABLE);

    -- 先记录
    local altarOption = ME.user.dbase:query("altar_option", {});
    altarOption[optionId] = (altarOption[optionId] or 0) + 1;
    ME.user.dbase:set("altar_option", altarOption);

    -- 标记用神灯许愿过
    BuffDataM.set("lamp_already_used", 1, 1);

    -- 永久计数的选项
    if query(optionId, "forever") then
        local foreverOption = ME.user.dbase:query("forever_altar_option", {});
        foreverOption[optionId] = (foreverOption[optionId] or 0) + 1;

        ME.user.dbase:set("forever_altar_option", foreverOption);
    end

    BonusM.clearBonusCollect();
    BonusM.startBonusCollect();

    for key, value in pairs(m) do
        if key == "summon" then
            -- 召唤灯神
            callAladdinLamp(value, pos);
            -- SummonM.doSummon(value);
        elseif key == "lamp_prop" then
            -- 加强神灯（宝物）属性
            local lampProp = ME.user.dbase:query("lamp_prop", {});

            -- 直接加进去
            for k, v in pairs(value) do
                lampProp[k] = (lampProp[k] or 0) + v;
            end
            ME.user.dbase:set("lamp_prop", lampProp);

            -- 刷新下装备属性
            PropM.refreshEffect(ME.user, "equip");

            -- 发布神灯升级事件
            EventMgr.fire(event.LAMP_UPGRADED, { ["star"] = getLampRank()});
        elseif key == "bonus" then
            -- 奖励
            if type(value[1]) == "table" then
                for _, bonus in pairs(value) do
                    BonusM.doBonus(bonus, "desert_altar_" .. optionId);
                end
            else
                BonusM.doBonus(value, "desert_altar_" .. optionId);
            end
        elseif key == "random_bonus" then
            -- 随机奖励
            local rand = DungeonM.getRandSeed("altar_rand_bonus") % #value;
            local bonus = value[rand + 1];
            BonusM.doBonus(bonus, "desert_altar_" .. optionId);
        elseif key == "prop" then
            -- 属性
            PropM.record(ME.user, value, "desert_altar");
        elseif key == "upgrade_summon" then
            -- 升级灯神
            local lampSummon = ME.user.dbase:query("lamp_summon", {});

            -- 直接加进去
            for k, v in pairs(value) do
                lampSummon[k] = (lampSummon[k] or 0) + v;
            end
            ME.user.dbase:set("lamp_summon", lampSummon);
        elseif key == "slime" then
            -- 冈布奥
            BonusM.doBonus({3, value, 1}, "altar_slime");

            -- 立马保存结果
            DungeonActionM.immediatelySave();

            -- 抛出事件，尝试完成成就：获得隐藏冈布奥
            EventMgr.fire(event.GET_HIDDEN_SLIME, { ["heroId"] = value, });
        else
            -- do nothing
        end
    end

    BonusM.endBonusCollect();

    local bonus = BonusM.getBonusCollect();
    if #bonus == 0 then
        bonus = { ["bonus"] = {} };
    else
        bonus = { ["bonus"] = bonus };
    end

    printTable(bonus, "selectOption");

    -- 抛出通用建筑奖励事件
    EventMgr.fire(event.COMM_BUILDING_BONUS, {
        ["pos"]   = pos,
        ["type"]  = GRID_TYPE_DESERT_ALTAR,
        ["bonus"] = bonus,
    });

    -- 抛出神灯许愿事件
    EventMgr.fire(event.GENIE_LAMP_VOW, {
        ["pos"]      = pos,
        ["altarId"]  = altarId,
        ["optionId"] = optionId,
    });

    BonusM.clearBonusCollect();

    local m = altars[altarId];
    local classId = m["submit"];
    -- 神灯之愿专属发动特效
    local prop = PropM.combine(ME.user, "aladdin", 1);
    if prop[3] > 0 and type(classId[7169]) == "number" then
        -- 触发特技发动事件
        EventMgr.fire(event.SPECIAL_SKILL_TRIGGER, { propId = prop[1] });
    end

    return true;
end

-- 判断是否阿拉丁神灯
function isAladdinLamp(classId)
    local info = EquipM.query(classId, "dbase");

    if 1 == info["aladdin_lamp"] then
        return true;
    else
        return false;
    end
end

-- 神灯的阶位
function getLampRank()
    local lampProp = ME.user.dbase:query("lamp_prop", {});
    local lampRank = lampProp["lamp_rank"] or 0;

    return math.min(7, 1 + math.modf(lampRank / 15));
end

-- 计算神灯额外属性
function getLampProp(classId)
    if not isAladdinLamp(classId) then
        return {};
    end

    -- 公式计算
    return FormulaM.invoke("CALC_ALADDIN_LAMP_PROPS", classId);
end

-- 召唤神灯巨人
function callAladdinLamp(classId, pos)
    local para = FormulaM.invoke("CALC_LAMP_SUMMON_PARA", classId, pos);

    -- TODO:
    para.action = CombatM.getRound();

    SummonM.doSummon(classId, para);

    return true;
end
